package cn.javaxxw.zxing.spring.boot.template;

import cn.javaxxw.zxing.spring.boot.client.BufferedImageLuminanceSource;
import cn.javaxxw.zxing.spring.boot.client.MatrixToImageWriter;
import cn.javaxxw.zxing.spring.boot.utils.BitMatrixUtils;
import cn.javaxxw.zxing.spring.boot.utils.ImageUtils;
import cn.javaxxw.zxing.spring.boot.utils.support.TextPainterFactory;
import com.google.zxing.*;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.sun.imageio.plugins.common.ImageUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * @author tuy
 * @description 条形码工具
 * @date 2021/1/21 16:36
 */
public class BarCodeTemplate {

    private final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * 条形码默认宽度和高度
     */
    private static final int BR_CODE_WIDTH = 120;

    private static final int BR_CODE_HEIGHT = 50;

    private static final String UTF8 = "UTF-8";

    private static final String DEFAULT_IMAGE_TYPE = "png";

    private static final String BASE64_PREFIX = "data:image/png;base64,";

    /**
     * 提供给编码器的附加参数
     */
    private static final Map<DecodeHintType, Object> HINTS_DECODE;

    static {
        HINTS_DECODE = new HashMap<>(1);
        HINTS_DECODE.put(DecodeHintType.CHARACTER_SET, UTF8);
    }

    /**
     * 将文本内容编码为条形码或二维码
     * @param content 文本内容
     * @param format 格式枚举
     * @param width 宽度
     * @param height 高度
     * @return {@link BitMatrix}
     */
    public  BufferedImage encode(String content, BarcodeFormat format, int width, int height) {
        BitMatrix bitMatrix = null;
        try {
            bitMatrix = BitMatrixUtils.bitMatrix(content, format, width, height);
        } catch (WriterException e) {
            log.error("编码异常！",e);
        }
        BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
        TextPainterFactory.getPainter(format).paintText(bufferedImage, content);
        return bufferedImage;
    }


    /**
     * 生成二维码图片
     * @param content 文本内容
     * @param width 宽度
     * @param height 高度
     * @return 二维码图片（黑白）
     */
    public  BufferedImage encode(String content, int width, int height) {
        return encode(content,BarcodeFormat.QR_CODE, width, height);
    }

    /**
     * 生成二维码图片
     * @param content 文本内容
     * @param format 格式枚举
     * @return
     */
    public  BufferedImage encode(String content, BarcodeFormat format) {
        return encode(content,format, BR_CODE_WIDTH, BR_CODE_HEIGHT);
    }

    /**
     * 生成二维码到输出流
     * @param content 文本内容
     * @param imageType 图片类型（图片扩展名），见{@link ImageUtil}
     * @param out 目标流
     * @param width 宽度
     * @param height 高度
     * @throws IOException IO异常
     */
    public  void encode(String content, String imageType, OutputStream out, int width, int height)  {
        final BufferedImage image = encode(content, width, height);
        if(StringUtils.isEmpty(imageType)){
            imageType = DEFAULT_IMAGE_TYPE;
        }
        try {
            ImageIO.write(image, imageType, out);
        } catch (IOException e) {
            log.error("发生IO异常！",e);
        }
    }

    /**
     * 生成二维码到输出流
     * @param content 文本内容
     * @param format 格式枚举
     * @param imageType 图片类型
     * @param out  目标流
     * @param width 宽度
     * @param height 高度
     */
    public  void encode(String content, BarcodeFormat format, String imageType, OutputStream out, int width, int height)  {
        final BufferedImage image = encode(content, format,width, height);
        if(StringUtils.isEmpty(imageType)){
            imageType = DEFAULT_IMAGE_TYPE;
        }
        try {
            ImageIO.write(image, imageType, out);
        } catch (IOException e) {
            log.error("发生IO异常！",e);
        }
    }

    /**
     * 生成二维码到输出流
     * @param content 文本内容
     * @param format 格式枚举
     * @param out 目标流
     */
    public  void encode(String content, BarcodeFormat format, OutputStream out){
        final BufferedImage image = encode(content, format,BR_CODE_WIDTH, BR_CODE_HEIGHT);
        try {
            ImageIO.write(image, DEFAULT_IMAGE_TYPE, out);
        } catch (IOException e) {
            log.error("发生IO异常！",e);
        }
    }


    /**
     * 生成条码（Base64）
     * @param content 文本内容
     * @param width 宽度
     * @param height 高度
     * @return
     */
    public  String encodeBase64(String content, int width, int height){
        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            BufferedImage image = encode(content,width,height);
            // 输出二维码图片流
            ImageIO.write(image, DEFAULT_IMAGE_TYPE, output);
            return BASE64_PREFIX + Base64.getEncoder().encodeToString(output.toByteArray());
        } catch (IOException e) {
            log.error("发生IO异常！",e);
        }
        return null;
    }


    /**
     * 生成条码（Base64）
     * @param content 文本内容
     * @param format 格式枚举
     * @param width 宽度
     * @param height  高度
     * @return
     * @throws IOException
     */
    public  String encodeBase64(String content,BarcodeFormat format, int width, int height){
        try (ByteArrayOutputStream output = new ByteArrayOutputStream();) {
            BufferedImage image = encode(content,format,width,height);
            // 输出二维码图片流
            ImageIO.write(image, DEFAULT_IMAGE_TYPE, output);
            return BASE64_PREFIX + Base64.getEncoder().encodeToString(output.toByteArray());
        } catch (IOException e) {
            log.error("发生IO异常！",e);
        }
        return null;
    }

    /**
     * 生成条码（Base64）
     * @param content 文本内容
     * @param format 格式枚举
     * @return
     */
    public  String encodeBase64(String content,BarcodeFormat format){
        try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            BufferedImage image = encode(content,format,BR_CODE_WIDTH, BR_CODE_HEIGHT);
            // 输出二维码图片流
            ImageIO.write(image, DEFAULT_IMAGE_TYPE, output);
            return BASE64_PREFIX + Base64.getEncoder().encodeToString(output.toByteArray());
        } catch (IOException e) {
            log.error("发生IO异常！",e);
        }
        return null;
    }


    /**
     * 解码条形码或二维码图片为文本
     * @param qrCodeInputStream 条形码或二维码输入流
     * @return 解码文本
     * @throws IOException IO异常
     * @throws NotFoundException 解码失败异常
     */
    public  String decode(InputStream qrCodeInputStream) throws IOException, NotFoundException {
        return decode(ImageUtils.read(qrCodeInputStream));
    }

    /**
     * 解码条形码或二维码图片为文本
     * @param qrCodeFile 条形码或二维码图片
     * @return 解码文本
     * @throws IOException IO异常
     * @throws NotFoundException 解码失败异常
     */
    public  String decode(File qrCodeFile) throws IOException, NotFoundException {
        return decode(ImageUtils.read(qrCodeFile));
    }


    /**
     * 将条形码或二维码图片解码为文本
     * @param image {@link Image} 条形码或二维码图片
     * @return 解码后的文本
     * @throws NotFoundException 解码失败异常
     */
    public  String decode(Image image) throws NotFoundException {
        final LuminanceSource source = new BufferedImageLuminanceSource(ImageUtils.toBufferedImage(image));
        final BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
        return new MultiFormatReader().decode(binaryBitmap, HINTS_DECODE).getText();
    }

}
